package com.gitee.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.gitee.constant.*;
import com.gitee.context.BaseContext;
import com.gitee.dto.SetPasswordDTO;
import com.gitee.entity.User;
import com.gitee.exception.LogOutException;
import com.gitee.exception.LoginFailedException;
import com.gitee.mapper.UserMapper;
import com.gitee.properties.JwtProperties;
import com.gitee.service.UserService;
import com.gitee.utils.JwtUtil;
import com.gitee.dto.PasswordLoginDTO;
import com.gitee.dto.PhoneLoginDTO;
import com.gitee.vo.UserVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private UserMapper userMapper;

    @Resource
    private JwtProperties jwtProperties;

    @Override
    public String getCode(String phone) {
        if (!phone.matches(RegexConstant.PHONE_REGEX)) {
            throw new LoginFailedException(LoginConstant.PHONE_ERROR);
        }

        String code = RandomUtil.randomNumbers(6);

        // 将验证码放到redis中
        // key为admin:login:code:电话号
        // value为code验证码
        redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_CODE + phone, code, 2, TimeUnit.MINUTES);
        return code;
    }

    /**
     * 登录成功后的动作
     *
     * @param user 登录的管理员对象
     * @return VO对象, 包含token
     */
    private UserVO loginSuccess(User user) {
        /*
         * 存在手机号则直接登录
         * 将用户保存在redis中
         * key以admin:login:admin:手机号作为key
         * value是admin对象
         */
        redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_USER + user.getPhone(), user);

        // 登录成功后, 销毁验证码
        // 确保一个验证码只能用一次
        redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_CODE + user.getPhone(), null);

        // 登录成功后封装jwt, 生成token
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.USER_ID, user.getId());
        String token = JwtUtil.createJWT(jwtProperties.getAdminSecretKey(), jwtProperties.getAdminTtl(), claims);

        // 封装adminVO对象, 用于前端交互
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        userVO.setToken(token);
        return userVO;
    }

    @Override
    @Transactional
    public UserVO phoneLogin(PhoneLoginDTO phoneLoginDTO) {
        // 手机号错误
        if (!phoneLoginDTO.getPhone().matches(RegexConstant.PHONE_REGEX)) {
            throw new LoginFailedException(LoginConstant.PHONE_ERROR);
        }

        // 从redis获取验证码
        // key为admin:login:code:电话号
        String code = (String) redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_CODE + phoneLoginDTO.getPhone());

        // 验证码失效
        if (code == null) {
            throw new LoginFailedException(LoginConstant.CODE_LOSE);
        }

        // 验证码不一致
        if (!code.equals(phoneLoginDTO.getCode())) {
            throw new LoginFailedException(LoginConstant.CODE_ERROR);
        }

        // 手机号和验证码都正确
        // 是否存在手机号
        User user = userMapper.selectByPhone(phoneLoginDTO.getPhone());

        if (user != null) {
            // 修改登录状态
            user.setLoginState(true);
            userMapper.updateById(user);
        } else {
            // 不存在手机号进行注册
            // 用户注册只有手机号和登录状态信息
            // 密码需要用户进行二次验证码修改
            user = new User();
            user.setLoginState(true);
            user.setPhone(phoneLoginDTO.getPhone());
            user.setNickname("user:" + RandomUtil.randomString(10));
            userMapper.insert(user);
        }

        return loginSuccess(user);
    }

    @Override
    public UserVO passwordLogin(PasswordLoginDTO passwordLoginVO) {
        User user = new User();
        BeanUtils.copyProperties(passwordLoginVO, user);
        user = userMapper.selectByPhoneAndPassword(user);

        if (user == null) {
            throw new LoginFailedException(LoginConstant.PHONE_OR_PASSWORD_IS_NOT_EXIST);
        }

        // 手机号和密码正确可以直接登录成功
        return loginSuccess(user);
    }

    @Override
    public void setPassword(SetPasswordDTO setPasswordDTO) {
        // 从redis获取验证码
        String code = (String) redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_CODE + setPasswordDTO.getPhone());

        // 验证码是否失效
        if (code == null || setPasswordDTO.getCode() == null) {
            throw new LoginFailedException(LoginConstant.CODE_LOSE);
        }

        // 获得真实验证码, 与用户输入的验证码进行对比
        // 验证码错误
        if (!code.equals(setPasswordDTO.getCode())) {
            throw new LoginFailedException(LoginConstant.CODE_ERROR);
        }

        // 验证码正确

        // 从redis获取该手机号的用户
        // 验证手机号是否是该用户提供的手机号
        // 获得手机号
        User user = (User) redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_USER + setPasswordDTO.getPhone());
        // 手机号错误
        if (user == null) {
            throw new LoginFailedException(LoginConstant.PHONE_ERROR);
        }

        // 到这代表用户存在, 可以修改密码
        user.setPassword(setPasswordDTO.getPassword());
        // 通过手机号修改密码
        userMapper.updateByPhone(user);
    }

    @Override
    public void loginOut() {
        Integer currentId = BaseContext.getCurrentId();
        User user = userMapper.selectById(currentId);
        if (user == null) {
            throw new LogOutException(LogOutConstant.LOGOUT_FAIL);
        }

        // 修改登录状态为离线
        user.setLoginState(false);
        userMapper.updateById(user);

        // 清空该用户相关的redis数据
        redisTemplate.delete(redisTemplate.keys(RedisConstant.USER_LOGIN_USER + "*"));
        redisTemplate.delete(redisTemplate.keys(RedisConstant.USER_LOGIN_CODE + "*"));

    }

    @Override
    public User getUser(Integer userId) {
        return userMapper.selectById(userId);
    }

    @Override
    public List<User> getUserByName(String name) {
        return userMapper.selectByName("*".equals(name) ? null : name);
    }

    @Override
    public User getUserByPhone(String phone) {
        return userMapper.selectByPhone(phone);
    }

    @Override
    public void setUser(User user) {
        userMapper.updateById(user);
    }
}
